#pragma once

#include<iostream>
#include<sys/types.h>  
#include<sys/socket.h> 
#include<string>
#include<strings.h> 
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstdlib>
#include<unistd.h>
#include<functional>
#include<unordered_map>

//using func_t =std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&,const std::string&,uint16_t)> func_t;

//using namespace std;

enum 
{
    SOCKET_ERR=1,BIND_ERR
};

uint16_t default_port=8888;
std::string default_ip="0.0.0.0";
const int size=1024;


class UdpServer
{
public:
    UdpServer(const uint16_t& port=default_port,const std::string& ip=default_ip):_sockfd(0),_port(port),_ip(ip),_isrunning(false)
    {}
    void Init()
    {
        //1.创建一个UDP套接字
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd<0)
        {
            perror("socket fail");
            exit(SOCKET_ERR);
        }
        std::cout<<"socket create successfully! socket is:"<<_sockfd<<std::endl;
        //2.绑定套接字，即告诉服务器它绑定的端口和IP地址是什么
        struct sockaddr_in local;//记录服务器的本地信息,但此时local的定义是在程序地址空间的用户级栈上
        bzero(&local,sizeof(local));//将本地信息先初始化成0
        local.sin_family=AF_INET;//表明sockaddr_in结构，使用的是IPV4通信标准
        local.sin_port=htons(_port);//记录服务器的端口号,由于该端口号将来是要作为自身属性数据，通过网络发送给对方的，所以我们需要保证我们的端口号是网络字节序
        local.sin_addr.s_addr=inet_addr(_ip.c_str());//记录服务器的ip地址,由于用户传来的是字符串风格的ip，所以我们得先string->uint32_t,再保证ip地址是网络字节序 
        //local.sin_addr.s_addr=INADDR_ANY;//接收 发送向本机任一ip地址的 信息

        //把local变量与内核中的_socket套接字相关联
        //所以，绑定的本质，就是将我们设置的这些数据（通信标准、端口号、ip）设置到内核中指定的套接字内
        int ret=bind(_sockfd,(const sockaddr*)&local,sizeof(local));
        if(ret<0)
        {
            std::cout<<"bind error,errno:"<<errno<<"strerror:"<<strerror(errno)<<std::endl;
            exit(BIND_ERR);
        }
        std::cout<<"socket bind successfully!"<<std::endl;
    }

    void CheckUser(const struct sockaddr_in& client,std::string client_ip,uint16_t client_port)
    {
        auto it=_online_user.find(client_ip);
        if(it==_online_user.end())
        {
            _online_user.insert({client_ip,client});
            std::cout<<"["<<client_ip<<":"<<client_port<<"] add to online_user."<<std::endl;
        }
    }
    void Broadcast(std::string client_ip,uint16_t client_port,const std::string& buffer)
    {
        for(const auto& user:_online_user)
        {
            std::string echo_result="[";
            echo_result+=client_ip;
            echo_result+=":";
            echo_result+=std::to_string(client_port);
            echo_result+="]# ";
            echo_result+=buffer;
            //响应给其它所有客户端
            if(inet_ntoa(user.second.sin_addr)!=client_ip)
            sendto(_sockfd,echo_result.c_str(),echo_result.size(),0,(const struct sockaddr*)(&user.second),sizeof(user.second));
        }
    }
    void Run(const func_t& func)
    {
        _isrunning=true;
        char buffer[size];//自定义用户缓冲区，将数据从套接字的接收缓冲区读取出来，放到这里，供用户使用
        while(_isrunning)
        {
            struct sockaddr_in client;//用于存放客户端的通信标准、端口号、ip等信息
            socklen_t len=sizeof(client);
            //服务器将数据从“接收缓冲区”中读取出来
            ssize_t n=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&client,&len);
            if(n<0)
            {
                std::cout<<"recvfrom fail,errno:"<<errno<<"strerror:"<<strerror(errno)<<std::endl;
                continue;
            }
            buffer[n]='\0';//字符串的尾巴
            //拿到客户端的ip地址和端口号
            uint16_t client_port=ntohs(client.sin_port);//ntohs，对16位短整型，完成网络字节序到主机字节序间的转换
            std::string client_ip=inet_ntoa(client.sin_addr); //inet_ntoa，两个功能：①网络字节序转主机字节序；
                                                                                    //②将原整型ip地址转为字符串类型的ip地址
            // CheckUser(client,client_ip,client_port);//若该用户是新用户，则添加；若是老用户，则无需操作
            // Broadcast(client_ip,client_port,buffer);//将收到的消息转发给所有客户端
            //处理数据
            // std::string echo_result=func(buffer,client_ip,client_port);
            std::string echo_result="我是服务器，我收到了你的数据："+std::string(buffer);//给客户端的应答
            std::cout<<"echo back: "<<echo_result<<std::endl;
            //响应对应的客户端
            sendto(_sockfd,echo_result.c_str(),echo_result.size(),0,(const struct sockaddr*)&client,len);
        }
    }
    ~UdpServer()
    {
        if(_sockfd>0)close(_sockfd);
    }
private:
    int _sockfd;//网络文件描述符
    std::string _ip;//服务的ip地址
    uint16_t _port;//服务器的端口号
    bool _isrunning;//服务器的运行状态
    std::unordered_map<std::string, struct sockaddr_in> _online_user;
};